<template>
  <div class="duyi-tooltip" v-on="outerEvents">
    <!-- 触发的元素，设置为一个插槽 -->
    <div class="duyi-tooltip__trigger" ref="triggerRef" v-on="events">
      <slot />
    </div>
    <!-- 要显示的提示内容 -->
    <!-- 考虑有些时候提示内容会比较复杂，因此这里仍然是提供插槽的形式 -->
    <Transition name="fade">
      <div class="duyi-tooltip__popper" ref="popperRef" v-if="isOpen">
        <slot name="content">
          {{ content }}
        </slot>
        <div id="arrow" data-popper-arrow></div>
      </div>
    </Transition>
  </div>
</template>

<script setup>
import { computed, reactive, ref, watch } from 'vue'
import propsObj from './props.js'
import { createPopper } from '@popperjs/core'
import { debounce } from 'lodash-es'

defineOptions({
  name: 'DuyiTooltip',
})
// 属性
const props = defineProps(propsObj)
// 自定义事件
const emit = defineEmits(['visible-change'])

// 存储触发元素
const triggerRef = ref(null)
// 存储提示框元素
const popperRef = ref(null)

// 首先，提示框是否要显示出来，我们这里需要一个变量来控制
const isOpen = ref(false)

// v-on可以通过对象的形式绑定事件
// 该对象用于存储各种事件，回头触发元素绑定事件的时候直接绑定对象里面的事件即可
let events = reactive({})
let outerEvents = reactive({})

// 存储 createPopper 返回的 popper 实例
let popperInstance = null

// 这里创建一个计算属性，该计算属性用于构建 createPopper 方法的第三个参数
const popperOptions = computed(() => ({
  placement: props.placement,
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 6],
      },
    },
  ],
}))

// 接下来需要创建具体的 popper 实例
// popper 实例应该是在 isOpen 法神变化的时候创建
watch(
  isOpen,
  newValue => {
    if (newValue) {
      // isOpen 的新值为 true 时，说明要显示提示框，所以就创建 popper 实例
      // 这里还需要 trigger 元素和一个 popper 元素
      if (triggerRef.value && popperRef.value) {
        // 创建 popper 实例
        popperInstance = createPopper(
          triggerRef.value,
          popperRef.value,
          popperOptions.value,
        )
      } else {
        popperInstance?.destroy()
      }
    }
  },
  {
    flush: 'post',
  },
)

// 延时触发
const openDebounce = debounce(() => {
  isOpen.value = true
  // 触发自定义事件
  emit('visible-change', true)
}, props.openDelay)

const closeDebounce = debounce(() => {
  isOpen.value = false
  // 触发自定义事件
  emit('visible-change', false)
}, props.closeDelay)

// 显示提示框
const open = () => {
  closeDebounce.cancel()
  openDebounce()
}
// 隐藏提示框
const close = () => {
  openDebounce.cancel()
  closeDebounce()
}

// 接下来需要给触发元素绑定相应事件
// 调用 attachEvents 方法，就能给触发元素绑定事件
const attachEvents = () => {
  // 判断事件触发的类型
  if (props.trigger === 'hover') {
    // 鼠标移入/移出触发
    events['mouseenter'] = open
    outerEvents['mouseleave'] = close
  } else if (props.trigger === 'click') {
    // 点击触发
    // 点击事件对应的回调就是根据 isOpen 的值来判断调用打开还是隐藏方法
    events['click'] = () => (isOpen.value ? close() : open())
  }
}
// 调用attachEvents 方法，绑定事件
// 手动触发就不需要绑定事件
if (!props.manual) {
  attachEvents()
}

// 向外暴露数据
defineExpose({
  show: open,
  hide: close,
})
</script>

<style lang="scss" scoped></style>
